package com.syp.envops.util.sftp;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Vector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import com.alibaba.fastjson.JSON;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import com.syp.envops.util.Constants;

/**
 * SFTP 工具类
 * 
 * @author syp
 */
public class SftpClient {
	/**
	 * 调测日志记录器。
	 */
	private static final Logger log = LoggerFactory.getLogger(SftpClient.class);

	private SftpConfiguration config = null;

	public SftpClient(SftpConfiguration config) {
		this.config = config;
	}

	/**
	 * 连接sftp服务器
	 */
	public ChannelSftp connect() {
		ChannelSftp sftp = new ChannelSftp();
		try {
			JSch jsch = new JSch();
			Session session = jsch.getSession(this.config.getUsername(), this.config.getHost(), this.config.getPort());
			session.setPassword(this.config.getPassword());
			Properties sshConfig = new Properties();
			sshConfig.put("StrictHostKeyChecking", "no");
			session.setConfig(sshConfig);
			// 设置超时时间为
			session.setTimeout(1000 * 60 * 5);
			session.connect();
			Channel channel = session.openChannel("sftp");
			channel.connect();
			sftp = (ChannelSftp) channel;
			if (config.getRootdir() != null) {
				sftp.cd(config.getRootdir());
			}

		} catch (Exception e) {
			log.error("JSchException : {}", e);
			throw new RuntimeException(e);
		}
		return sftp;
	}

	/**
	 * sftp 新增目录
	 *
	 * @param path
	 */
	public void mkDir(String path) {
		ChannelSftp sftp = null;
		try {
			sftp = this.connect();
			sftp.cd(path);
		} catch (SftpException e) {
			try {
				if (ChannelSftp.SSH_FX_NO_SUCH_FILE == e.id) {
					sftp.mkdir(path);
				} else {
					log.error("JSch cd error", e);
					throw new RuntimeException(e);
				}
			} catch (SftpException ee) {
				log.error("JSch mkDir error", ee);
				throw new RuntimeException(ee);
			}
		} finally {
			if (sftp != null) {
				sftp.disconnect();
			}

		}
	}

	/**
	 * sftp 上传
	 *
	 * @param data
	 * @param path
	 */
	public void upload(String path, byte[] data) {
		InputStream input = null;
		ChannelSftp sftp = null;
		try {
			sftp = this.connect();

			String dir = path.substring(0, path.lastIndexOf("/"));
			this.mcd(dir);
			input = new ByteArrayInputStream(data);
			sftp.put(input, path);
		} catch (Exception e) {
			log.error("JSch upload error", e);
			throw new RuntimeException(e);
		} finally {
			try {
				if (input != null) {
					input.close();
				}
			} catch (IOException e) {
				log.error("close inputstream:input error");
			}
			if (sftp != null) {
				sftp.disconnect();
			}

		}
	}

	/**
	 * sftp 下载
	 * 
	 * @param path
	 * @return
	 */
	public byte[] download(String path) {
		byte[] data = null;
		ChannelSftp sftp = null;
		ByteArrayOutputStream out = null;
		try {
			sftp = this.connect();

			out = new ByteArrayOutputStream();
			sftp.get(path, out);
			data = out.toByteArray();
		} catch (SftpException e) {
			log.error("JSch download error", e);
			throw new RuntimeException(e);
		} finally {
			try {
				if (out != null) {
					out.flush();
					out.close();
				}
			} catch (IOException e) {
				log.error("close inputstream:input error");
			}

			if (sftp != null) {
				sftp.disconnect();
			}

		}
		return data;
	}

	/**
	 * 逐级目录切换，遇到目录不存在则创建
	 * 
	 * @param dir
	 */
	public void mcd(String dir) {
		ChannelSftp sftp = null;
		try {
			sftp = this.connect();
			try {
				sftp.cd(dir);
			} catch (SftpException e) {
				log.error("切换目录失败，尝试逐级目录切换，遇到目录不存在则创建");

				String[] subDirs = dir.split("/");
				if (dir.charAt(0) == '/') {
					sftp.cd("/");
				}

				for (String sd : subDirs) {
					if (sd != null && !sd.equals("")) {
						try {
							sftp.cd(sd);
						} catch (SftpException e1) {
							sftp.mkdir(sd);
							sftp.cd(sd);
						}
					}
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (sftp != null) {
				sftp.disconnect();
			}
		}
	}

	/**
	 * sftp 删除文件
	 * 
	 * @param path
	 */
	public void rm(String path) {
		ChannelSftp sftp = null;
		try {
			sftp = this.connect();
			sftp.rm(path);
		} catch (SftpException e) {
			log.error("JSch download error", e);
			throw new RuntimeException(e);
		} finally {
			if (sftp != null) {
				sftp.disconnect();
			}

		}
	}

	/**
	 * sftp 删除目录以及目录下文件
	 *
	 * @param path
	 */
	public void rmdir(String path) {
		ChannelSftp sftp = null;
		sftp = this.connect();
		try {
			rmdir(path, sftp);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (sftp != null) {
				sftp.disconnect();
			}
		}
	}

	private void rmdir(String path, ChannelSftp sftp) {
		try {
			if (sftp == null) {
				sftp = this.connect();
			}
			List<SftpFile> sftpFiles = list(path, false, sftp);
			if (!CollectionUtils.isEmpty(sftpFiles)) {
				for (SftpFile sftpFile : sftpFiles) {
					if (!sftpFile.getIsDir()) {
						sftp.rm(sftpFile.getAbsolutePath());
					} else {
						rmdir(sftpFile.getAbsolutePath(), sftp);
					}
				}
			}
			sftp.rmdir(path);
		} catch (SftpException e) {
			log.error("JSch rmdir error", e);
			throw new RuntimeException(e);
		}
	}

	/**
	 * sftp 下载
	 * 
	 * @param path
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public SftpFile get(String path) {
		SftpFile sftpFile = null;
		ChannelSftp sftp = null;
		ByteArrayOutputStream out = null;
		try {
			sftp = this.connect();

			Vector<LsEntry> fileList = sftp.ls(path);
			Iterator<LsEntry> fileListIt = fileList.iterator();
			if (fileListIt.hasNext()) {
				LsEntry lsEntity = (LsEntry) fileListIt.next();

				sftpFile = new SftpFile();
				sftpFile.setServerId(this.config.getServerId());
				sftpFile.setUsername(this.config.getUsername());
				sftpFile.setHost(this.config.getHost());
				sftpFile.setFilename(lsEntity.getFilename());
				sftpFile.setAbsolutePath(path);
				sftpFile.setFileLen(lsEntity.getAttrs().getSize());
				sftpFile.setCreateTime(new Date(Long.valueOf(lsEntity.getAttrs().getATime() * 1000L)));
				sftpFile.setLastUpdate(new Date(Long.valueOf(lsEntity.getAttrs().getMTime() * 1000L)));
				sftpFile.setIsDir(lsEntity.getLongname().charAt(0) == 'd');

				out = new ByteArrayOutputStream();
				sftp.get(path, out);
				sftpFile.setContent(out.toString(Constants.CHARSET_UTF8));
			}
		} catch (Exception e) {
			log.error("JSch get error", e);
			throw new RuntimeException(e);
		} finally {
			try {
				if (out != null) {
					out.flush();
					out.close();
				}
			} catch (IOException e) {
				log.error("close inputstream:input error");
			}

			if (sftp != null) {
				sftp.disconnect();
			}

		}
		return sftpFile;
	}

	/**
	 * 列出目录下的文件
	 */
	public List<SftpFile> list() {
		List<SftpFile> list = null;

		ChannelSftp sftp = this.connect();
		Filter defaultFilter = new DefaultFilter();
		list = this.list(this.config.getRootdir(), defaultFilter, true, sftp);
		if (sftp != null) {
			sftp.disconnect();
		}
		return list;
	}

	/**
	 * 列出目录下的文件
	 */
	public List<SftpFile> list(Filter filter) {
		List<SftpFile> list = null;

		ChannelSftp sftp = this.connect();
		list = this.list(this.config.getRootdir(), filter, true, sftp);
		if (sftp != null) {
			sftp.disconnect();
		}
		return list;
	}

	/**
	 * 列出目录下的文件
	 */
	public List<SftpFile> list(String dir, boolean recursion) {
		List<SftpFile> list = null;

		ChannelSftp sftp = this.connect();
		Filter defaultFilter = new DefaultFilter();
		if (dir != null && !dir.isEmpty()) {
			list = this.list(dir, defaultFilter, recursion, sftp);
		} else {
			list = this.list(this.config.getRootdir(), defaultFilter, recursion, sftp);
		}

		if (sftp != null) {
			sftp.disconnect();
		}

		return list;
	}

	/**
	 * 列出目录下的文件
	 */
	public List<SftpFile> list(String dir, Filter filter, boolean recursion) {
		List<SftpFile> list = null;

		ChannelSftp sftp = this.connect();
		if (dir != null && !dir.isEmpty()) {
			list = this.list(dir, filter, recursion, sftp);
		} else {
			list = this.list(this.config.getRootdir(), filter, recursion, sftp);
		}

		if (sftp != null) {
			sftp.disconnect();
		}

		return list;
	}

	/**
	 * 列出目录下的文件
	 */
	public List<SftpFile> list(String dir, boolean recursion, ChannelSftp sftp) {
		List<SftpFile> list = null;
		Filter defaultFilter = new DefaultFilter();
		if (dir != null && !dir.isEmpty()) {
			list = this.list(dir, defaultFilter, recursion, sftp);
		} else {
			list = this.list(this.config.getRootdir(), defaultFilter, recursion, sftp);
		}
		return list;
	}

	/**
	 * 列出目录下的文件
	 */
	@SuppressWarnings("unchecked")
	public List<SftpFile> list(String dir, Filter filter, boolean recursion, ChannelSftp sftp) {
		List<SftpFile> list = new LinkedList<SftpFile>();
		try {
			Vector<LsEntry> fileList = null;

			String absolutePath = null;
			if (dir.charAt(0) == '/') {
				absolutePath = dir;
			} else {
				absolutePath = this.config.getRootdir() + "/" + dir;
			}
			fileList = sftp.ls(dir);

			Iterator<LsEntry> fileListIt = fileList.iterator();
			while (fileListIt.hasNext()) {
				LsEntry lsEntity = (LsEntry) fileListIt.next();
				if (!filter.doFilter(lsEntity)) {
					continue;
				}

				SftpFile sftpFile = new SftpFile();
				sftpFile.setServerId(this.config.getServerId());
				sftpFile.setUsername(this.config.getUsername());
				sftpFile.setHost(this.config.getHost());
				sftpFile.setFilename(lsEntity.getFilename());
				sftpFile.setAbsolutePath(absolutePath + "/" + lsEntity.getFilename());
				sftpFile.setFileLen(lsEntity.getAttrs().getSize());
				sftpFile.setCreateTime(new Date(Long.valueOf(lsEntity.getAttrs().getATime())));
				sftpFile.setLastUpdate(new Date(Long.valueOf(lsEntity.getAttrs().getMTime())));
				sftpFile.setIsDir(lsEntity.getLongname().charAt(0) == 'd');

				if (recursion && sftpFile.getIsDir()) {
					String nextDir = dir + "/" + sftpFile.getFilename();
					sftpFile.setChildren(this.list(nextDir, recursion, sftp));
				}
				list.add(sftpFile);
			}
			list.sort((SftpFile f1, SftpFile f2) -> {
				if (f1.getIsDir().equals(f2.getIsDir()))
					return f1.getFilename().compareTo(f2.getFilename());					
				return f2.getIsDir().compareTo(f1.getIsDir());
			});
		} catch (SftpException e) {
			log.error("JSch list error", e);
			throw new RuntimeException(e);
		}
		return list;
	}

	public static void main(String[] args) {
		SftpConfiguration config = new SftpConfiguration();
		config.setHost("10.141.4.223");
		config.setPort(22);
		config.setUsername("root");
		config.setPassword("jd07gm09");
		config.setRootdir("/opt/dev");

		List<String> ll = new ArrayList<>();
		ll.add(".sh");
		Filter filter = new ExtnameExcludeFilter(ll);

		SftpClient sftpClient = new SftpClient(config);
		ChannelSftp sftp = sftpClient.connect();
		List<SftpFile> list = sftpClient.list("srun/", filter, true, sftp);
		System.err.println(JSON.toJSONString(list));

	}

}